#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "../comm/util.hpp"
#include "../comm/log.hpp"

/*
 *   只负责编译
 */
namespace ns_compiler
{
    using namespace ns_util; //工具类的命名空间
    using namespace ns_log;
    class Compiler
    {
    public:
        Compiler() {}
        ~Compiler() {}
        /***************************************
         * 功能：对用户提交的代码进行编译并且形成临时文件
         * 返回值：编译成功：true 编译失败 bool
         * 输入参数：要编译的代码文件名,该文件名是根据毫秒级时间戳+原子递增形成的唯一文件名
         **************************************/

        //如果用户提供的代码，在后端被处理之后形成的file_name为:code,
        //那么我们需要自己完成对文件后缀的拼接并且完成形成文件的路径拼接
        //    code->./temp/code.cpp-->g++编译时候的源文件代码
        //    code->./temp/code.exe-->g++编译形成可执行文件代码
        //    code->./temp/code.stderr-->g++编译出错输出的出错信息存储的文件
        static bool Compile(const std::string &file_name) // file_name是不带路径，不带后缀的文件名
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "编译用户提交代码文件:" << PathUtil::Src(file_name) << "的时候创建子进程失败..."
                           << "\n";
                return false;
            }
            else if (pid == 0)
            { //子进程：调用编译器，完成对代码的编译功能
                /**************************************
                 * 为了处理编译出错，所以要保存编译出错的信息
                 * 但是编译出错的信息是默认输出到stderr
                 * 解决思路，重定向到自己打开的文件：xxx.stderr中
                 * ***********************************/

                // 1. 预先打开编译失败的文件
                umask(0);
                int stderr_fd = open(PathUtil::CompileError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
                if (stderr_fd < 0)
                {
                    LOG(WARNING) << "编译用户提交的代码文件：" << PathUtil::Src(file_name) << " 的时候,没有形成对应的xxx.stderr后缀的文件..."
                                 << "\n";
                    exit(1);
                }
                // 2. 重定向g++的错误信息
                dup2(stderr_fd, STDERR_FILENO);

                // 3.调用编译器对用户提交代码（该代码也需要后端处理形成临时文件）进行编译，形成临时文件保存在 ./temp/ 目录下
                execlp("g++", "g++", "-o",
                       PathUtil::Exe(file_name).c_str(),
                       PathUtil::Src(file_name).c_str(), "-std=c++11", "-D", "COMPILER_ONLINE", nullptr);

                LOG(ERROR) << "启动编译器g++错误，无法编译用户提交的代码..."
                           << "\n";
                exit(2);
            }
            else
            { //父进程判断是否编译成功

                waitpid(pid, nullptr, 0);
                //判断是否编译成功：准则，查看g++是否编译生成可执行文件即可

                if (FileUtil::IsFileExist(PathUtil::Exe(file_name)))
                {
                    LOG(INFO) << "g++编译成功用户提交的代码文件: " << PathUtil::Src(file_name) << "\n形成的可执行程序的代码文件: " << PathUtil::Exe(file_name) << "\n";
                    return true;
                }
                else
                {
                    LOG(ERROR) << "g++编译失败用户提交的代码文件:" << PathUtil::Src(file_name) << " 没有形成可执行程序..."
                               << "\n";
                    return false;
                }
            }
        }
    };
}
